﻿#include "../exercise.h"
#include <cstring>

// READ: 模板非类型实参 <https://zh.cppreference.com/w/cpp/language/template_parameters#%E6%A8%A1%E6%9D%BF%E9%9D%9E%E7%B1%BB%E5%9E%8B%E5%AE%9E%E5%8F%82>

template<unsigned int N, class T>
struct Tensor {
    unsigned int shape[N];
    T *data;

    Tensor(unsigned int const shape_[N]) {
        unsigned int size = 1;
        // TODO: 填入正确的 shape 并计算 size

        for (unsigned int i = 0; i < N; ++i) {
            shape[i] = shape_[i];
            size *= shape[i];
        }

        data = new T[size];
        std::memset(data, 0, size * sizeof(T));
    }
    ~Tensor() {
        delete[] data;
    }

    // 为了保持简单，禁止复制和移动
    Tensor(Tensor const &) = delete;
    Tensor(Tensor &&) noexcept = delete;

    T &operator[](unsigned int const indices[N]) {
        return data[data_index(indices)];
    }
    T const &operator[](unsigned int const indices[N]) const {
        return data[data_index(indices)];
    }

private:
    // unsigned int data_index(unsigned int const indices[N]) const {
    //     unsigned int index = 0;
    //     for (unsigned int i = 0; i < N; ++i) {
    //         ASSERT(indices[i] < shape[i], "Invalid index");
    //         // TODO: 计算 index
    //     }
    //     return index;
    // }

    unsigned int data_index(unsigned int const indices[N]) const {
        unsigned int index = 0;
        unsigned int stride = 1;
        
        // 从最高维开始计算索引（行优先顺序）
        for (int i = N - 1; i >= 0; --i) {
            ASSERT(indices[i] < shape[i], "Invalid index");
            index += indices[i] * stride;
            stride *= shape[i];
        }
        
        return index;
    }
};

// ---- 不要修改以下代码 ----
int main(int argc, char **argv) {
    {
        unsigned int shape[]{2, 3, 4, 5};
        auto tensor = Tensor<4, int>(shape);

        unsigned int i0[]{0, 0, 0, 0};
        tensor[i0] = 1;
        ASSERT(tensor[i0] == 1, "tensor[i0] should be 1");
        ASSERT(tensor.data[0] == 1, "tensor[i0] should be 1");

        unsigned int i1[]{1, 2, 3, 4};
        tensor[i1] = 2;
        ASSERT(tensor[i1] == 2, "tensor[i1] should be 2");
        ASSERT(tensor.data[119] == 2, "tensor[i1] should be 2");
    }
    {
        unsigned int shape[]{7, 8, 128};
        auto tensor = Tensor<3, float>(shape);

        unsigned int i0[]{0, 0, 0};
        tensor[i0] = 1.f;
        ASSERT(tensor[i0] == 1.f, "tensor[i0] should be 1");
        ASSERT(tensor.data[0] == 1.f, "tensor[i0] should be 1");

        unsigned int i1[]{3, 4, 99};
        tensor[i1] = 2.f;
        ASSERT(tensor[i1] == 2.f, "tensor[i1] should be 2");
        ASSERT(tensor.data[3683] == 2.f, "tensor[i1] should be 2");
    }
    return 0;
}



/*

模板非类型实参?是什么
就是本来<> 里面是 class T, 现在是 unsigned int N ，可以用来当作变量使用

这些模板都是运行时候动态确定的，（N 是传入的数组的大小确定的， T 是数组的类型确定的）错了，是类创建时候后面的<> 里面定义的


我来帮你实现这个N维张量类模板。这是一个很好的例子来展示模板非类型参数的使用。

方法思路
模板非类型参数：使用unsigned int N作为维度数
索引计算：将N维索引转换为线性索引
内存管理：正确计算总大小并分配内存



代码解释
1. 构造函数
Tensor(unsigned int const shape_[N]) {
    unsigned int size = 1;
    for (unsigned int i = 0; i < N; ++i) {
        shape[i] = shape_[i];
        size *= shape[i];
    }
    data = new T[size];
    std::memset(data, 0, size * sizeof(T));
}
复制shape数组
计算总元素数量（各维度乘积）
分配内存并初始化为0
2. 索引计算（关键部分）
unsigned int data_index(unsigned int const indices[N]) const {
    unsigned int index = 0;
    unsigned int stride = 1;
    
    // 从最高维开始计算索引（行优先顺序）
    for (int i = N - 1; i >= 0; --i) {
        ASSERT(indices[i] < shape[i], "Invalid index");
        index += indices[i] * stride;
        stride *= shape[i];
    }
    
    return index;
}
索引计算原理（以4D张量[2,3,4,5]为例）：

第0维（最高维）：stride = 3×4×5 = 60
第1维：stride = 4×5 = 20
第2维：stride = 5
第3维（最低维）：stride = 1
索引计算公式：index = i0×60 + i1×20 + i2×5 + i3×1

3. 测试用例验证
第一个测试用例：shape = [2,3,4,5], indices = [1,2,3,4]

index = 1×(3×4×5) + 2×(4×5) + 3×5 + 4
      = 1×60 + 2×20 + 15 + 4
      = 60 + 40 + 15 + 4 = 119 ✓
第二个测试用例：shape = [7,8,128], indices = [3,4,99]

index = 3×(8×128) + 4×128 + 99
      = 3×1024 + 512 + 99
      = 3072 + 512 + 99 = 3683 ✓
模板非类型参数的优势
使用template<unsigned int N, class T>的好处：

编译时确定维度：维度数在编译时就知道，可以进行更好的优化
类型安全：不同维度的张量是不同类型，避免运行时错误
性能优化：循环展开等编译时优化成为可能
替代实现
也可以使用正向循环计算索引：

unsigned int data_index(unsigned int const indices[N]) const {
    unsigned int index = 0;
    unsigned int multiplier = 1;
    
    for (int i = N - 1; i >= 0; --i) {
        index += indices[i] * multiplier;
        multiplier *= shape[i];
    }
    
    return index;
}
或者使用累积乘积：

unsigned int data_index(unsigned int const indices[N]) const {
    unsigned int index = indices[0];
    for (unsigned int i = 1; i < N; ++i) {
        index = index * shape[i] + indices[i];
    }
    return index;
}
测试验证
程序运行后会通过所有测试：

4D张量索引计算正确
3D张量索引计算正确
边界索引访问正确
数据存储和访问一致
这个实现提供了一个通用的N维张量类模板，支持任意维度的张量操作，类似于NumPy的多维数组。



这是一个很好的问题！C++通过const重载机制来决定调用哪个版本。编译器根据调用对象的常量性（constness） 来自动选择正确的函数版本。
选择规则
编译器根据调用对象的常量性来决定：

非常量对象 → 调用非常量版本
常量对象 → 调用常量版本

具体示例
Tensor<3, float> tensor(shape);        // 非常量对象
const Tensor<3, float> const_tensor(shape); // 常量对象

// 调用非常量版本（返回 T&）
float &value = tensor[{1, 2, 3}];
value = 5.0f;  // 可以修改

// 调用常量版本（返回 const T&）
const float &const_value = const_tensor[{1, 2, 3}];
// const_value = 5.0f;  // 错误！不能修改常量引用

编译器如何区分
编译器通过函数签名来区分：
// 非常量版本：隐含的 this 指针是 Tensor*
T& operator[](unsigned int const indices[N]);

// 常量版本：隐含的 this 指针是 const Tensor*
const T& operator[](unsigned int const indices[N]) const;

当调用tensor[i]时：

如果tensor是非常量对象，this指针类型是Tensor*，匹配非常量版本
如果tensor是常量对象，this指针类型是const Tensor*，匹配常量版本

实际应用场景
void process_tensor(const Tensor<3, float> &tensor) {
    // 这里只能调用常量版本，因为参数是const引用
    float value = tensor[{0, 0, 0}];  // 调用 const 版本
    // 不能修改，因为返回的是 const float&
}

void modify_tensor(Tensor<3, float> &tensor) {
    // 这里调用非常量版本，可以修改值
    tensor[{0, 0, 0}] = 42.0f;  // 调用非const版本
}

为什么需要两个版本？

安全性：常量对象不应该被修改
灵活性：非常量对象既可以读取也可以写入
性能：避免不必要的拷贝（使用引用而不是值）

重载解析过程
当编译器看到tensor[i]时：

检查tensor的类型（常量或非常量）
查找匹配的operator[]重载
选择与对象常量性匹配的版本
如果只有常量版本，非常量对象也可以调用（但返回const引用）
如果只有非常量版本，常量对象无法调用（编译错误）

重要特性

const是函数签名的一部分：func()和func() const是不同的函数
常量对象只能调用常量成员函数
非常量对象优先调用非常量版本，但也可以调用常量版本
返回值类型也参与重载决策

这种设计模式在C++标准库中广泛使用，比如std::vector::operator[]也有const和非const两个版本。


为什么没有调用const版本？
所有tensor对象都是非常量的：auto tensor = Tensor<4, int>(shape)创建的是非常量对象
所有操作都是读写操作：既有读取也有写入，需要非常量引用
没有const对象或const引用：测试代码中没有创建const Tensor对象


总结
虽然测试代码中没有显式使用const版本，但提供const重载是良好的编程实践：

安全性：防止意外修改const对象
接口完整性：提供完整的const-correct接口
设计意图：明确表达"只读"操作的意图
兼容性：与C++标准库和其他库保持一致
在实际项目中，const版本会被频繁使用，特别是在：

函数参数传递
const成员函数中
多线程只读访问
防止意外修改的场景中
*/